Skip to content

fix(templates): do not mount the CodeArtifact secret while building project code - #526

Closed
stephen-wang24 wants to merge 1 commit into
nextfrom
stephen/dockerfile-secret-scope
Closed

stephen-wang24 wants to merge 1 commit into
nextfrom
stephen/dockerfile-secret-scope

Conversation

@stephen-wang24

@stephen-wang24 stephen-wang24 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Dockerfile-uv.j2 installs in two steps and mounted the broker secret on both. The
second one runs uv sync after COPY project, which builds the agent's own project
— and a PEP 517 build backend is project-controlled code. With the secret mounted there
it can read the CodeArtifact token, and the build has network.

Raised by Greptile on #525. Fixing it here instead: #525 is the release-please branch, so
a push there gets overwritten by the next regeneration.

Reproduced first

The vector needs only files the templates already copy — a pyproject.toml declaring
backend-path = ["project"] and one module under project/. No Dockerfile edit. That is
exactly the threat PRIVATE_INDEX.md already claims to defend against:

It is a contributed change to a project file, where a one-line URL edit is far less
conspicuous in review than an exfiltration command in a Dockerfile.

What the project's own build backend captured during uv sync:

secret-file:  index-url = https://aws:FAKE%2BTOKEN%2FVALUE%3D@.../simple/
env password: FAKE+TOKEN/VALUE=          <- decoded, not just the encoded URL

Not only the mounted file — the decoded token too, the one the template extracts into
UV_INDEX_SCALE_PYPI_PASSWORD. Written to the image layer here; a real one would POST it.

The fix, and proof nothing is lost

The second step no longer mounts the secret. Dependencies — private ones included — are
already installed by the first step.

Verified against a package published only to a local private index and absent from
public PyPI (pypi.org/simple/fake-private-obs/ returns 404), which is the sgp-obs case:

secret on both steps secret on step 1 only
build succeeds yes yes
package from the private index installs yes yes
build backend reads the secret file yes <absent>
build backend reads the decoded token yes <unset>

So the mirror still works for exactly what it exists for, and project-controlled code no
longer sees the credential.

Scope

  • 19 × Dockerfile-uv.j2 — secret dropped from the project-build step only
  • PRIVATE_INDEX.md — documents which step holds the secret and why

Dockerfile.j2 (the requirements.txt variant) never had this problem — it installs and
only then copies the project, so no project-controlled code runs while the secret is
mounted. Left alone.

Template-only; no Python changed.

Testing

Both variants built with BuildKit against a local private index. One caveat: the full
rendered template could not finish building on my machine — its apt-get layer needs
~1.2 GB and the local VM ran out of disk — so the behaviour above was measured on a
minimal build with the identical two-step shape, and the rendered template was checked
structurally (one secret mount, on the dependency step only). Worth a real build in CI.

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR appears safe to merge. Adding a test for the secret boundary would make the fix harder to undo by accident.

What we checked:

  • Project builds cannot read the broker token: No. The secret is mounted before project code is copied. The later project build gets only the uv cache mount.

Summary

The Dockerfile-uv.j2 templates now mount the CodeArtifact secret only while installing dependencies, not while building the agent's own project. The private package index still works, while project-controlled build code no longer receives the credential.

Diagram
sequenceDiagram
    participant Build as Docker build
    participant Broker as Broker secret
    participant UV as uv
    participant Index as Private index
    participant Backend as Project build backend

    Build->>Broker: Mount codeartifact-pip-conf
    Build->>UV: uv sync --no-install-project
    UV->>Index: Install dependencies with token
    Build-->>Broker: End dependency step and unmount secret
    Build->>Build: COPY project
    Build->>UV: uv sync without broker secret
    UV->>Backend: Build and install project
    Note over Backend: Secret file and token variables are absent
Loading

Reviews (1) · Last reviewed commit: "fix(templates): do not mount the CodeArt..."

…roject code

`Dockerfile-uv.j2` installs in two steps and mounted the broker secret on both.
The second step runs `uv sync` after `COPY project`, which builds the agent's
own project — and a PEP 517 backend is project-controlled code, either an
in-tree module named by `backend-path` or whatever `[build-system] requires`
pulls in. With the secret mounted there it can read the token, and the build
has network.

Reproduced before fixing. The vector needs only files the templates already
copy — `pyproject.toml` declaring `backend-path = ["project"]`, plus one module
under `project/` — so no Dockerfile change is required, which is exactly the
threat PRIVATE_INDEX.md already says it defends against: a contributed change
to a project file is far less conspicuous in review than a curl in a
Dockerfile. What the backend captured:

    secret-file:  index-url = https://aws:FAKE%2BTOKEN%2FVALUE%3D@.../simple/
    env password: FAKE+TOKEN/VALUE=        <- decoded, not just the encoded URL

The second step no longer mounts it. Dependencies, private ones included, are
already installed by the first step, so nothing is lost. Verified against a
package published ONLY to a local private index and absent from public PyPI
(pypi.org returns 404 for it), which is the sgp-obs case:

    | build succeeds                     | both steps: yes | step 1 only: yes |
    | private-index package installs     | yes             | YES              |
    | backend reads the secret file      | yes             | <absent>         |
    | backend reads the decoded token    | yes             | <unset>          |

`Dockerfile.j2`, the requirements.txt variant, never had this: it installs and
only then copies the project, so no project-controlled code runs while the
secret is mounted. Left alone.

Raised by Greptile on #525. Fixing it here rather than there — #525 is the
release-please branch, and a push to it would be overwritten by the next
regeneration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@stephen-wang24

Copy link
Copy Markdown
Contributor Author

@greptileai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant